package com.ydlclass.ChannelHandler.handler.ChannelDuplexHandler;

import com.ydlclass.Transport.message.MessageFormatConstant;
import com.ydlclass.Transport.message.Request.RequestPayload;
import com.ydlclass.Transport.message.Request.YrpcRequest;
import com.ydlclass.enumeration.RequestTypeEnum;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageCodec;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.util.List;

/**
 * 双向的，既可以出站，也可以入站
 * 请求编码处理器 Encode,出栈的时候用的
 * 对发送的ByteBuf做一些填充
 *
 * 4B  magic(魔术)
 * 1B  version(版本)
 * 2B  header length (头部长度)
 * 4B  full  length (总长度)
 * 1B  request type (请求类型)
 * 1B  serialize (序列化类型)
 * 1B  compress (压缩类型)
 * 8B  requestId (请求ID)
 *
 * 22 个字节是固定长度的字节.,,以及不定长的body
 * 不定长的body  YrpcRequest
 *
 *
 *  @Author duWenJian
 *  @Date 2024/2/20 14:43
 *  @Version 1.0
 */
@Slf4j
public class YrpcMessageCodeHandler extends MessageToMessageCodec<ByteBuf, YrpcRequest> {
    @Override
    protected void encode(ChannelHandlerContext ctx, YrpcRequest yrpcRequest, List<Object> out) throws Exception {
        ByteBuf buf = ctx.alloc().buffer();
        // 魔术值 占4个字节
        buf.writeBytes(MessageFormatConstant.MAGIC);
        // 版本号 占1个字节
        buf.writeByte(MessageFormatConstant.VERSION);
        // 头部长度 占2个字节
        buf.writeShort(MessageFormatConstant.HEAD_LENGTH);
        // todo 总长度 占4个字节,不知道body的长度,
        //  保存当前写指针的位置 writeIndex(写指针) 往后移动4个位置，等body长度计算出来之后，再计算FULL_LENGTH
        buf.markWriterIndex();
        buf.writerIndex(buf.writerIndex() + MessageFormatConstant.FULL_FIELD_LENGTH);
        // 请求类型 占1个字节 序列化类型 占1个字节 压缩类型 占1个字节
        buf.writeByte(yrpcRequest.getRequestType());
        buf.writeByte(yrpcRequest.getSerializeType());
        buf.writeByte(yrpcRequest.getCompressType());
        // 请求Id 占8个字节
        buf.writeLong(yrpcRequest.getRequestId());

        // 如果是心跳请求,需要独立的处理一下总长度
//        if (RequestTypeEnum.HeartbeatRequest.getId() == yrpcRequest.getRequestType()){
//            int index = buf.writerIndex();
//            buf.resetWriterIndex();
//            buf.writeInt(MessageFormatConstant.HEAD_LENGTH);
//            buf.writerIndex(index);
//            out.add(buf);
//            return;
//        }

        // 请求体payload 经过计算算出
        byte[] body = getBodyBytes(yrpcRequest.getRequestPayload());
        if (body != null){
            buf.writeBytes(body);
        }
        int bodyLength = body == null ? 0 : body.length;
        // 保存当前写指针的位置
        int index = buf.writerIndex();
        buf.resetWriterIndex();
        buf.writeInt(MessageFormatConstant.HEAD_LENGTH + bodyLength);
        // 写指针回来
        buf.writerIndex(index);
        out.add(buf);
    }

    private byte[] getBodyBytes(RequestPayload requestPayload) {
        //  todo 心跳请求没有payload
        if (requestPayload == null){
            return null;
        }
        // todo 将对象转化为字节数组  序列化(选择了jdk的), 压缩,
        //  针对不同的消息类型做不同的处理,如果payload是空的，心跳请求的请求，返回空的字节数组
        //  希望可以通过一些设计模式，oop的方式可以让我们修改序列化方式、压缩方式,自由配置
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(requestPayload);

            // todo 压缩

            return baos.toByteArray();
        } catch (IOException e) {
            log.error("序列化发生异常:{}",e);
            throw new RuntimeException("对body进行序列化发生错误");
        }
    }

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out){
        //既然重写了decode方法，一定不要忘了最后加入list链表中，不然很麻烦
        // 1、 解析魔术值 4字节 , 校验魔术值
        byte [] magic = new byte[MessageFormatConstant.MAGIC.length];
        msg.readBytes(magic);
        for (int i = 0; i < magic.length; i++) {
            if (magic[i] != MessageFormatConstant.MAGIC[i]){
                throw new RuntimeException("获得请求魔术值不合法!!!");
            }
        }
        // 2、解析版本号 1字节 , 校验版本号
        byte Version_Id = msg.readByte();
        if (Version_Id > MessageFormatConstant.VERSION){
            throw new RuntimeException("获得请求版本不被支持!!!");
        }
        // 3、解析头部长度 2字节 , 校验头部长度
        short headLength = msg.readShort();
        if (headLength != MessageFormatConstant.HEAD_LENGTH){
            throw new RuntimeException("获得请求头部长度不合法!!!");
        }
        // 4、解析总长度 4字节 , 校验总长度
        int fullLength = msg.readInt();

        // 5、解析请求类型 1字节 , 校验请求类型
        byte requestType = msg.readByte();

        // 6、解析序列化类型 1字节 , 校验序列化类型
        byte serializeType = msg.readByte();
        if (serializeType != MessageFormatConstant.SERIALIZE_TYPE){
            throw new RuntimeException("获得请求序列化类型不合法!!!");
        }
        // 7、解析压缩类型 1字节 , 校验压缩类型
        byte compressType = msg.readByte();
        if (compressType != MessageFormatConstant.COMPRESS_TYPE){
            throw new RuntimeException("获得请求压缩类型不合法!!!");
        }
        // 8、解析请求Id 8字节 , 校验请求Id
        long requestId = msg.readLong();
        if (requestId != 1L){
            throw new RuntimeException("获得请求Id不合法!!!");
        }

        // ---------------------封装YrpcRequest----------------------------------
        YrpcRequest yrpcRequest = YrpcRequest.builder()
                .requestType(requestType)
                .serializeType(serializeType)
                .compressType(compressType)
                .requestId(requestId)
                .build();

        // 心跳请求直接返回,没有负载,不需要进行接下来的操作
        if (requestType == RequestTypeEnum.HeartbeatRequest.getId()) {
            out.add(yrpcRequest);
            return;
        }


        // 9、解析body
        int bodyLength = fullLength - headLength;
        byte[] payload = new byte[bodyLength];
        msg.readBytes(payload);
        // 将字节数组进行解压缩、反序列化
        // todo 解压缩

        // todo 反序列化
        RequestPayload requestPayload = null;
        try (ByteArrayInputStream bais = new ByteArrayInputStream(payload);
             ObjectInputStream ois  = new ObjectInputStream(bais);) {
            requestPayload = (RequestPayload)ois.readObject();
        } catch (IOException | ClassNotFoundException e) {
            log.error("请求:{}反序列化发生异常:{}",requestId,e);
            throw new RuntimeException("对body进行反序列化发生错误");
        }
        yrpcRequest.setRequestPayload(requestPayload);

        out.add(yrpcRequest);
    }
}
